package com.tengju.bff.interfaces.shared.servlet;

import com.tengju.bff.interfaces.covercharge.constant.CoverChargeErrorCode;
import com.tengju.bff.interfaces.shared.ApiResponse;
import com.tengju.data.domain.model.alert.AlertEvent;
import com.tengju.data.domain.model.alert.AlertService;
import com.tengju.user.domain.model.login.UserType;
import com.tengju.user.domain.shared.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Iterator;
import java.util.Map;


@Slf4j
@RestControllerAdvice
@SuppressWarnings("unchecked")
public class GlobalExceptionHandlerAdvice {

    @Autowired
    AlertService alertService;

    @ExceptionHandler(value = IllegalArgumentException.class)
    public ApiResponse<?> handleIllegalArgumentException(IllegalArgumentException e) {
        log.error("error : ", e);
        return ApiResponse.newFailure(CoverChargeErrorCode.PARAM_ERROR.getCode(), e.getMessage());
    }

    @ExceptionHandler(value = NumberFormatException.class)
    public ApiResponse<?> handleNumberFormatException(NumberFormatException e) {
        log.error("error : ", e);
        return ApiResponse.newFailure(CoverChargeErrorCode.PARAM_ERROR.getCode(), e.getMessage());
    }

    @ExceptionHandler(BindException.class)
    public ApiResponse handleBindException(BindException e) {
        log.error("error : ", e);
        for (ObjectError error : e.getBindingResult().getAllErrors()) {
            if (error instanceof FieldError) {
                String field = ((FieldError) error).getField();
                return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), "参数" + field + "类型错误");
            }
            return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), error.getDefaultMessage());
        }
        return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ApiResponse handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.error("error : ", e);
        return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), "参数" + e.getName() + "类型错误");
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("error : ", e);
        for (ObjectError error : e.getBindingResult().getAllErrors()) {
            return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), error.getDefaultMessage());
        }
        return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), e.getMessage());
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ApiResponse handleValidation(ConstraintViolationException e) {
        log.error("error : ", e);
        for (ConstraintViolation<?> error : e.getConstraintViolations()) {
            return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), error.getMessage());
        }
        return ApiResponse.newFailure(ResultCodeEnum.PARAMETER_INVALID.getCode(), e.getMessage());
    }

    @ExceptionHandler({
            com.tengju.bff.interfaces.shared.InterfaceException.class,
            com.tengju.bff.application.shared.ApplicationException.class,
            com.tengju.user.application.shared.ApplicationException.class,
            com.tengju.user.domain.shared.DomainException.class,
            com.tengju.user.domain.shared.BizException.class,
            com.tengju.activity.domain.shared.DomainException.class,
            com.tengju.activity.application.shared.ApplicationException.class,
            com.tengju.support.domain.shared.DomainException.class,
            com.tengju.support.application.shared.ApplicationException.class,
            com.tengju.motivation.domain.shared.DomainException.class,
            com.tengju.motivation.application.shared.ApplicationException.class,
            com.tengju.payment.application.shard.ApplicationException.class,
            com.tengju.payment.domain.shard.DomainException.class,
            com.tengju.settlement.domain.shared.DomainException.class,
            com.tengju.settlement.infrastructure.shared.InfraException.class,
            com.tengju.settlement.application.shared.ApplicationException.class})
    public ApiResponse handlerBizException(RuntimeException e) {
        log.error("error : ", e);
        return ApiResponse.newFailure(ResultCodeEnum.BIZ_ERROR.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse handlerBizException(Exception e) {
        //权限
        if (e instanceof UnauthorizedException) {
            log.warn("权限不足 : ", e);
            return ApiResponse.newFailure(ResultCodeEnum.INSUFFICIENT_AUTHORITY);
        }
        if (e instanceof MultipartException) {
            log.error("文件上传异常 : ", e);
            return ApiResponse.newFailure(ResultCodeEnum.FILE_ERROR);
        }
        log.error("error : ", e);
        alert(e);
        return ApiResponse.newFailure(ResultCodeEnum.SYS_EXCEPTION);
    }

    private void alert(Exception e) {
        try {
            UserServletContext servletContext = ServletContextUtil.getServletContext();
            AlertEvent alertEvent = new AlertEvent("未知异常：");
            if (servletContext != null) {
                alertEvent.addItem("path", servletContext.getRequestPath());
                alertEvent.addItem("userType", servletContext.getUserType());
                alertEvent.addItem("userId", servletContext.getUserType() == UserType.USER ? servletContext.getUserId() : null);
                alertEvent.addItem("staffId", servletContext.getUserType() == UserType.STAFF ? servletContext.getStaffId() : null);
                HttpServletRequest request = servletContext.getRequest();
                alertEvent.addItem("params", configRequestParam(request));
                alertEvent.addItem("context", servletContext.toString());
            }
            StringBuilder message = new StringBuilder();
            if (e != null) {
                message.append(e.getClass()).append(": ").append(e.getMessage()).append("\n");
                StackTraceElement[] elements = e.getStackTrace();
                int i = 0;
                for (StackTraceElement stackTraceElement : elements) {
                    message.append("\t").append(stackTraceElement.toString()).append("\n");
                    if (++i == 5) {
                        break;
                    }
                }
            }
            alertEvent.addItem("异常信息", message.toString());
            alertService.alert(alertEvent);
        } catch (Exception ex) {
            log.error("告警异常", ex);
        }
    }

    private String configRequestParam(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        if (request == null) {
            return sb.toString();
        }
        if (request.getParameterMap() != null && request.getParameterMap().size() > 0) {
            sb.append("{");
            Map<String, String[]> paramMap = request.getParameterMap();
            Iterator<Map.Entry<String, String[]>> iterator = paramMap.entrySet().iterator();

            while (iterator.hasNext()) {
                Map.Entry<String, String[]> entry = iterator.next();
                sb.append(entry.getKey()).append("=");
                for (Object obj : entry.getValue()) {
                    sb.append(obj);
                }
                if (iterator.hasNext()) {
                    sb.append(",");
                }
            }
            sb.append("}");
        } else if (request.getMethod() != null && request.getMethod().equals("POST")) {
            sb.append("|{");
            if (request.getAttribute("payLoad") != null) {
                sb.append(request.getAttribute("payLoad"));
            }
            sb.append("}");
        }
        return sb.toString();
    }
}
